use std::collections::HashMap;
use std::path::Path;

use tracing::debug;

use bc_base::{
    BlockHeight, BlockId,
    block::{Block, BlockInDb, BlockIndexCache, BlockIndexNode},
};
use bc_block_data::BlockAndIndexStorage;

use crate::top_storage::TopStorageError;

#[derive(Debug)]
pub struct BlockAndIndexStorageWithCache {
    block_index_cache: BlockIndexCache,
    block_and_index_storage: BlockAndIndexStorage,
}

impl BlockAndIndexStorageWithCache {
    pub async fn new(db_root_dir: &Path) -> Self {
        let block_and_index_storage = BlockAndIndexStorage::new(db_root_dir).unwrap();
        let mut t = Self {
            block_index_cache: HashMap::new(),
            block_and_index_storage,
        };

        bc_util::execute_function_elapsed("load_to_block_index_cache", async {
            t.block_and_index_storage
                .load_block_index_to_cache(&mut t.block_index_cache)
                .await
                .unwrap();
            debug!("block_index_cache len: {}", t.block_index_cache.len());
            for it in t.block_index_cache.iter() {
                debug!("block_index_cache: {}", it.0);
            }
        })
        .await;

        t
    }

    pub async fn get_item_number(&self) -> usize {
        let n1 = self.block_index_cache.len();
        #[cfg(debug_assertions)]
        {
            let n2 = self.block_and_index_storage.get_item_number().await;
            debug_assert_eq!(n1, n2);
        }
        n1
    }
    pub async fn flush_block_and_index(&self) -> Result<(), TopStorageError> {
        self.block_and_index_storage
            .flush_block_and_index_db()
            .await?;
        Ok(())
    }

    pub async fn delete_block_and_index_sync(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), TopStorageError> {
        self.delete_block_from_db(block_id).await?;
        self.delete_block_index_node_sync(block_id).await?;

        Ok(())
    }

    pub async fn write_block_and_index_sync(
        &mut self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), TopStorageError> {
        // save block to db
        self.write_block_to_db(block_id, block).await?;

        // save block index to db and cache
        self.write_block_index_node_sync(block_id, block).await?;

        Ok(())
    }

    pub async fn block_index_exists(&self, block_id: &BlockId) -> bool {
        let t1 = self.block_index_cache.contains_key(block_id);
        #[cfg(debug_assertions)]
        {
            let t2 = self
                .block_and_index_storage
                .block_exists_in_db(block_id)
                .await;
            debug_assert_eq!(t1, t2);
        }

        t1
    }
}

// for block
impl BlockAndIndexStorageWithCache {
    pub async fn read_block_in_db(&self, block_id: &BlockId) -> Result<BlockInDb, TopStorageError> {
        let r = self
            .block_and_index_storage
            .read_block_from_db(block_id)
            .await?;

        Ok(r)
    }

    async fn write_block_to_db(
        &mut self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), TopStorageError> {
        let block_in_db = BlockInDb::new(block);
        self.block_and_index_storage
            .write_block_to_db(block_id, &block_in_db)
            .await?;
        Ok(())
    }

    async fn delete_block_from_db(&mut self, block_id: &BlockId) -> Result<(), TopStorageError> {
        self.block_and_index_storage
            .delete_block_from_db(block_id)
            .await?;
        Ok(())
    }
}

// for block index
impl BlockAndIndexStorageWithCache {
    pub fn iter_block_index(&self) -> impl Iterator<Item = (&BlockId, &BlockIndexNode)> {
        self.block_index_cache.iter()
    }

    pub fn get_prev_n_timestame(&self, block_id: &BlockId, num: usize) -> Vec<u32> {
        let mut time_seq = Vec::with_capacity(num);
        let mut block_index = self.block_index_cache.get(block_id);
        debug!("get_median_time_past for block: {}", block_id);
        assert!(block_index.is_some());

        while let Some(ind) = block_index {
            time_seq.push(ind.get_timestamp());

            if time_seq.len() >= num {
                break;
            }

            block_index = self.block_index_cache.get(ind.get_prev_block_id());
        }

        time_seq
    }

    pub fn read_block_index_node(&self, block_id: &BlockId) -> Option<BlockIndexNode> {
        self.block_index_cache.get(block_id).cloned()
    }

    pub async fn set_next_block_id_sync(
        &mut self,
        block_id: &BlockId,
        next_blk_id: &BlockId,
    ) -> Result<(), TopStorageError> {
        if let Some(mut block_index) = self.block_index_cache.remove(block_id) {
            block_index.set_next_block_id(next_blk_id);
            self.update_block_index_node_sync(block_id, block_index)
                .await?;
        } else {
            return Err(TopStorageError::CacheMissOfBlockIndexNode(format!(
                "[set next blk] BlockIndexNode for block ID {} not found in cache",
                block_id
            )));
        }

        Ok(())
    }

    pub async fn reset_next_block_id_sync(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), TopStorageError> {
        if let Some(mut block_index) = self.block_index_cache.remove(block_id) {
            block_index.reset_next_block_id();
            self.update_block_index_node_sync(block_id, block_index)
                .await?;
        } else {
            return Err(TopStorageError::CacheMissOfBlockIndexNode(format!(
                "[reset next blk] BlockIndexNode for block ID {} not found in cache",
                block_id
            )));
        }

        Ok(())
    }

    pub async fn update_block_height_sync(
        &mut self,
        block_id: &BlockId,
        block_height: BlockHeight,
    ) -> Result<(), TopStorageError> {
        if let Some(mut block_index) = self.block_index_cache.remove(block_id) {
            block_index.set_height(block_height);
            self.update_block_index_node_sync(block_id, block_index)
                .await?;
        } else {
            return Err(TopStorageError::CacheMissOfBlockIndexNode(format!(
                "[update block height] BlockIndexNode for block ID {} not found in cache",
                block_id
            )));
        }

        Ok(())
    }

    async fn write_block_index_node_sync(
        &mut self,
        block_id: &BlockId,
        block: &Block,
    ) -> Result<(), TopStorageError> {
        let block_index_node = BlockIndexNode::new_by_block(block);
        self.block_and_index_storage
            .write_block_index_to_db(block_id, &block_index_node)
            .await?;
        self.block_index_cache.insert(*block_id, block_index_node);
        Ok(())
    }

    async fn update_block_index_node_sync(
        &mut self,
        block_id: &BlockId,
        block_index_node: BlockIndexNode,
    ) -> Result<(), TopStorageError> {
        self.block_and_index_storage
            .write_block_index_to_db(block_id, &block_index_node)
            .await?;
        self.block_index_cache.insert(*block_id, block_index_node);

        Ok(())
    }

    async fn delete_block_index_node_sync(
        &mut self,
        block_id: &BlockId,
    ) -> Result<(), TopStorageError> {
        self.block_index_cache.remove(block_id);
        self.block_and_index_storage
            .delete_block_index_from_db(block_id)
            .await?;

        Ok(())
    }
}
